/*
 * Copyright (C) 2008 Peter Kling
 *
 * This file is part of 'A Kind of Billiard', a graphical billiard simulation.
 *
 * 'A Kind of Billiard' is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * 'A Kind of Billiard' is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * 'A Kind of Billiard'. If not, see <http://www.gnu.org/licenses/>.
 */

package cg2.billiard;

import javax.media.opengl.GL;

import cg2.mathematics.MathMatrix;
import cg2.mathematics.MathVector;
import cg2.mathematics.Mathematics;
import cg2.misc.OpenGLDrawable;
import cg2.models.GLModel;
import cg2.models.WavefrontObjLoader;

/**
 * @brief A cue indicator that is used to visualize several information of the cue ball of a billiard table.
 * 
 * This standard implementation uses a arrow model that is drawn at the cue balls position. Its direction and length
 * indicate the corresponding properties of the next cue stroke.
 */
public class CueIndicator implements OpenGLDrawable {
	// billiard table of this indicator
	private BilliardTable billiardTable;
	
	// model data
	public static final String MODEL_CUEINDICATOR = "models/billiard/cueIndicator.obj";
	private GLModel model;
	
	// misc data
	public final double CUEVELOCITY_MIN  = 0.1;
	public final double CUEVELOCITY_MAX  = 4.0;
	public final double CUEVELOCITY_STEP = 0.1;
	private double     cueAngleH    = 0.0;                       // angle watching down on the table (horizontally)
	private double     cueAngleV    = 0.0;                       // angle the cue is drawn vertically
	private MathVector cueRotAxisH  = null;                      // axis for horizontally rotation
	private MathVector cueRotAxisV  = null;                      // axis for vertically rotation
	private double     cueVelocity  = 0.0;
	
	/**
	 * @brief Standard constructor.
	 */
	public CueIndicator(BilliardTable billiardTable) {
		this.billiardTable = billiardTable;
	}
	
	/**
	 * @brief Decrease the velocity the cue ball will be played with.
	 */
	public void decreaseCueVelocity() {
		cueVelocity = Math.max(cueVelocity-CUEVELOCITY_STEP, CUEVELOCITY_MIN);
	}
	
	/**
	 * @brief Draws the cue indicator.
	 */
	public void draw(GL gl) {
		MathVector cueBall_velocity = billiardTable.getCueBall().getVelocity();
		if (cueBall_velocity.norm() > 0.25)
			return;
		MathVector cueBall_position = billiardTable.getCueBall().getPosition();
		double     cueBall_radius   = billiardTable.getCueBall().getRadius();
		double     table_height     = billiardTable.getHeight();
		
		gl.glColor4d(1.0, 1.0, 1.0, 1.0);
		gl.glPushMatrix();
		gl.glTranslated(cueBall_position.get(0), table_height + cueBall_radius + cueBall_position.get(1),
				cueBall_position.get(2));
		gl.glRotated(cueAngleV, cueRotAxisV.get(0), cueRotAxisV.get(1), cueRotAxisV.get(2));
		gl.glRotated(cueAngleH, cueRotAxisH.get(0), cueRotAxisH.get(1), cueRotAxisH.get(2));
		gl.glTranslated(-1.25*cueBall_radius, 0., 0.);
		gl.glScaled(1.+(cueVelocity-CUEVELOCITY_MIN)/(CUEVELOCITY_MAX-CUEVELOCITY_MIN), 1., 1.);
		model.draw(gl);
		gl.glPopMatrix();
	}
	
	/**
	 * @brief Returns the velocity vector the ball will have after a cue stroke.
	 */
	public MathVector getCueStrokeVelocity() {
		MathMatrix rotMatrixH      = Mathematics.get3DRotationMatrix(Math.PI*cueAngleH/180., cueRotAxisH);
		MathMatrix rotMatrixV      = Mathematics.get3DRotationMatrix(Math.PI*cueAngleV/180., cueRotAxisV);
		MathVector velocityVector = Mathematics.mul(Mathematics.getUnitVector(3, 1), cueVelocity);
		velocityVector = Mathematics.mul(rotMatrixH, velocityVector);
		velocityVector = Mathematics.mul(rotMatrixV, velocityVector);
		return velocityVector;
	}
	
	/**
	 * @brief Increases the velocity the cue ball will be played with.
	 */
	public void increaseCueVelocity() {
		cueVelocity = Math.min(cueVelocity+CUEVELOCITY_STEP, CUEVELOCITY_MAX);
	}
	
	/**
	 * @brief Initializes the cue indicator.
	 */
	public void init(GL gl) {
		model = WavefrontObjLoader.loadObject(gl, MODEL_CUEINDICATOR);
		reset();
	}
	
	/**
	 * @brief Resets the cue indicator (direction and velocity the ball will be played with).
	 */
	public void reset() {
		cueRotAxisH  = Mathematics.getUnitVector(3, 2);
		cueRotAxisV  = Mathematics.getUnitVector(3, 3);
		cueVelocity  = 2.;
		cueAngleH    = 0.0;
		cueAngleV    = 0.0;
	}
	
	/**
	 * @brief Increases the angles for horizontally and vertically rotations of the cue by the given values.
	 */
	public void rotateCue(double angleH, double angleV) {
		cueAngleH += angleH;
		cueAngleV += angleV;
		
		// rotate the rotation axis for vertically rotation
		cueRotAxisV = Mathematics.mul(Mathematics.get3DRotationMatrix(Math.PI*angleH/180.,cueRotAxisH),cueRotAxisV);
	}
	
	/**
	 * @brief Rotates the cue some degree 'downwards'.
	 * 
	 * Using this method you can rotate the cue such that the ball is played upwards up to some maximum angle.
	 */
	public void rotateCue_down() {
		rotateCue(0., 5.);
	}
	
	/**
	 * @brief Rotates the cue some degree around the y-axis in mathematical positive sense.
	 */
	public void rotateCue_left() {
		rotateCue(5., 0.);
	}
	
	/**
	 * @brief Rotates the cue some degree around the y-axis in mathematical negative sense.
	 */
	public void rotateCue_right() {
		rotateCue(-5., 0.);
	}
	
	/**
	 * @brief Rotates the cue some degree 'upwards'.
	 * 
	 * Using this method you can rotate the cue such that the ball is played downwards up to some maximum angle.
	 */
	public void rotateCue_up() {
		rotateCue(0., -5.);
	}
	
	/**
	 * @brief Sets the velocity the cue ball will have after the next stroke with the cue.
	 */
	public void setCueVelocity(double value) {
		cueVelocity = value;
	}
}
